/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include "imu.h"
#include <mc/log.h>

namespace dev {

imu::imu(mc::engine::service* service) : m_service(service) {
    if (!m_service) {
        MC_THROW(mc::invalid_argument_exception, "Service pointer cannot be null");
    }

    // 创建 IPMB 实例
    m_ipmb = std::make_unique<mc::ipmb>(m_service);
    dlog("IMU protocol initialized");
}

imu::~imu() {
    dlog("IMU protocol destructed");
}

std::optional<std::vector<uint8_t>>
imu::get_info_from_imu(bool is_local, imu_pcie_device_req_t& pcie_device_info) {
    // 将旧的接口转换为新的接口
    imu_pcie_info pcie_info = {.is_local = is_local,
                               .cpu_id   = pcie_device_info.socket_id,
                               .address  = static_cast<uint8_t>(pcie_device_info.pci_info_address),
                               .function_num = pcie_device_info.function,
                               .device_num   = pcie_device_info.device,
                               .bus_num      = pcie_device_info.bus,
                               .read_length  = pcie_device_info.rlen};

    auto response_data = send_pmu_pci_request(pcie_info);
    if (!response_data.has_value()) {
        elog("PMU PCI info request failed");
        return std::nullopt;
    }

    std::vector<uint8_t> parsed_data =
        parse_pmu_pci_response(response_data.value(), pcie_info.read_length);
    if (parsed_data.empty()) {
        elog("PMU PCI info response parsing failed");
        return std::nullopt;
    }

    if (parsed_data.size() >= pcie_info.read_length) {
        dlog("Successfully got PCI info from IMU, data length: ${length}",
             ("length", parsed_data.size()));
        return parsed_data;
    } else {
        elog(
            "PCI info data length insufficient: expected ${expected} bytes, actual ${actual} bytes",
            ("expected", pcie_info.read_length)("actual", parsed_data.size()));
        return std::nullopt;
    }
}

std::optional<std::vector<uint8_t>> imu::send_pmu_pci_request(const imu_pcie_info& pcie_info) {
    if (!m_ipmb) {
        elog("IPMB instance not initialized");
        return std::nullopt;
    }

    try {
        uint8_t cpu_id = 0;
        if (pcie_info.is_local) {
            cpu_id = (pcie_info.cpu_id & 0x07) | 0x40;
        } else {
            cpu_id = pcie_info.cpu_id & 0x07;
        }

        uint8_t addr1 = pcie_info.address & 0xff;
        uint8_t addr2 =
            ((pcie_info.function_num & 0x07) << 4) | ((pcie_info.device_num & 0x01) << 7);
        uint8_t addr3 = ((pcie_info.device_num >> 1) & 0x0f) | ((pcie_info.bus_num & 0x0f) << 4);
        uint8_t addr4 = pcie_info.bus_num >> 4;

        uint8_t length = (pcie_info.read_length == 4) ? 3 : 4;

        std::vector<uint8_t> request_data = {
            0xDB,   0x07, 0x00, // 固定头部
            cpu_id,             // CPU ID
            addr1,              // PCI地址1
            addr2,              // PCI地址2
            addr3,              // PCI地址3
            addr4,              // PCI地址4
            length              // 读取长度
        };

        auto response = m_ipmb->send_request(mc::ipmb_channel_type::me, // ME 通道
                                             mc::ipmi_net_fn::oem,      // NetFn: OEM
                                             0x44,                      // Cmd: 自定义命令
                                             request_data,
                                             5000 // 5秒超时
        );

        if (response && mc::ipmi_utils::is_success(response->completion_code)) {
            dlog("PMU PCI info request sent successfully, response data length: ${length}",
                 ("length", response->data.size()));
            return response->data;
        } else {
            if (response) {
                elog("PMU command execution failed: ${cc}",
                     ("cc", mc::ipmi_utils::format_completion_code(response->completion_code)));
            } else {
                elog("PMU command execution failed: no response");
            }
            return std::nullopt;
        }
    } catch (const std::exception& e) {
        elog("Exception occurred while sending PMU PCI info request: ${error}",
             ("error", e.what()));
        return std::nullopt;
    }
}

std::vector<uint8_t> imu::parse_pmu_pci_response(const std::vector<uint8_t>& response_data,
                                                 uint8_t                     read_length) {
    std::vector<uint8_t> result;

    if (response_data.empty()) {
        elog("Response data is empty");
        return result;
    }

    try {
        // 跳过3字节头部，读取指定长度的数据
        if (response_data.size() < 3) {
            elog("Response data too short: expected at least 3 bytes, got ${actual}",
                 ("actual", response_data.size()));
            return result;
        }

        // 从第4个字节开始读取数据，能读多少读多少
        size_t available_data = response_data.size() - 3;
        size_t bytes_to_read  = std::min(static_cast<size_t>(read_length), available_data);

        for (size_t i = 0; i < bytes_to_read; ++i) {
            result.push_back(response_data[3 + i]);
        }

        dlog("Successfully parsed PMU PCI info response, expected ${read} bytes, actually got "
             "${actual} bytes",
             ("read", read_length)("actual", result.size()));

    } catch (const std::exception& e) {
        elog("Exception occurred while parsing PMU PCI info response: ${error}",
             ("error", e.what()));
    }

    return result;
}

bool imu::get_pmu_status(pmu_version_info& version_info) {
    if (!m_ipmb) {
        elog("IPMB instance not initialized");
        return false;
    }

    try {
        // 发送 Get Device ID 命令
        auto response = m_ipmb->send_request(mc::ipmb_channel_type::me, // ME 通道
                                             mc::ipmi_net_fn::app,      // NetFn: Application
                                             0x01,                      // Cmd: Get Device ID
                                             {},                        // 无数据
                                             5000                       // 5秒超时
        );

        if (response && mc::ipmi_utils::is_success(response->completion_code)) {
            if (response->data.size() >= 4) {
                version_info.device1      = response->data[0];
                version_info.device2      = response->data[1];
                version_info.pmu_version1 = response->data[2];
                version_info.pmu_version2 = response->data[3];
                dlog("PMU status retrieved successfully");
                return true;
            } else {
                elog("PMU status response data insufficient: expected 4 bytes, actual ${length} "
                     "bytes",
                     ("length", response->data.size()));
                return false;
            }
        } else {
            if (response) {
                elog("Failed to get PMU status: ${cc}",
                     ("cc", mc::ipmi_utils::format_completion_code(response->completion_code)));
            } else {
                elog("Failed to get PMU status: no response");
            }
            return false;
        }
    } catch (const std::exception& e) {
        elog("Exception occurred while getting PMU status: ${error}", ("error", e.what()));
        return false;
    }
}

bool imu::send_ipmi_request(uint8_t* reqdata, uint32_t reqdata_len, std::string& rspdata) {
    if (!m_ipmb) {
        elog("IPMB instance not initialized");
        return false;
    }

    try {
        if (reqdata == nullptr || reqdata_len == 0) {
            elog("IPMI request data invalid");
            return false;
        }

        std::vector<uint8_t> request_data(reqdata, reqdata + reqdata_len);

        auto response = m_ipmb->send_request(mc::ipmb_channel_type::me, // ME 通道
                                             mc::ipmi_net_fn::oem,      // NetFn: OEM
                                             0x44,                      // Cmd: 自定义命令
                                             request_data,
                                             5000 // 5秒超时
        );

        if (response && mc::ipmi_utils::is_success(response->completion_code)) {
            rspdata.assign(response->data.begin(), response->data.end());
            dlog("IPMI request sent successfully");
            return true;
        } else {
            if (response) {
                elog("IPMI command execution failed: ${cc}",
                     ("cc", mc::ipmi_utils::format_completion_code(response->completion_code)));
            } else {
                elog("IPMI command execution failed: no response");
            }
            return false;
        }
    } catch (const std::exception& e) {
        elog("Exception occurred while sending IPMI request: ${error}", ("error", e.what()));
        return false;
    }
}

} // namespace dev